iT邦幫忙

2024 iThome 鐵人賽

DAY 11
1
自我挑戰組

從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用系列 第 11

[Day 11] Rust 的模組與套件管理:如何引用模組

  • 分享至 

  • xImage
  •  

當專案規模逐漸增大時,良好的代碼組織變得至關重要。Rust 提供了一個強大且靈活的模組系統,讓開發者能夠將程式碼結構化,並以高效的方式進行模組化開發。同時,Rust 的套件管理工具 Cargo 也提供了強大的依賴管理和包分發功能,讓開發者能輕鬆地管理項目和依賴。

在這篇文章中,我們將介紹如何在 Rust 中組織代碼,並深入探討 Rust 的模組系統以及 Cargo 的套件管理機制。


一、Rust 的模組系統

Rust 的模組系統是將程式碼劃分為不同邏輯單位的方式,它使得開發者能夠更清晰地管理代碼,避免重複定義與命名衝突。Rust 中的模組可以用來封裝結構體、函數、類別等,並且模組可以嵌套。

模組的基本使用

Rust 的模組通常定義在一個 mod 關鍵字下,模組可以定義在同一個文件中,也可以拆分到不同的文件。模組中的內容預設是私有的,但可以使用 pub 關鍵字將其公開,以便外部訪問。

範例:單文件中的模組

mod math_utils {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn subtract(a: i32, b: i32) -> i32 {
        a - b
    }
}

fn main() {
    let sum = math_utils::add(5, 3);
    let difference = math_utils::subtract(5, 3);
    println!("5 + 3 = {}", sum);
    println!("5 - 3 = {}", difference);
}

說明:

  1. 模組定義mod math_utils 創建了一個名為 math_utils 的模組,內部定義了兩個公開的函數 addsubtract
  2. 公開內容:使用 pub 關鍵字將函數設為公開,使得我們可以在 main 函數中使用 math_utils::addmath_utils::subtract 來調用這些函數。

二、模組拆分成多個文件

專案規模較大時,將模組拆分到不同的文件中可以讓代碼更加整潔與可維護。在 Rust 中,模組可以定義在外部文件中,並在主文件中進行引用。

步驟:

  1. 建立模組文件:在專案目錄下建立一個 src/math_utils.rs 文件,並在其中定義函數。
  2. 主文件引用模組:在主文件 src/main.rs 中引用該模組。

src/math_utils.rs 文件:

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

src/main.rs 文件:

mod math_utils;

fn main() {
    let sum = math_utils::add(10, 20);
    let difference = math_utils::subtract(30, 15);
    println!("10 + 20 = {}", sum);
    println!("30 - 15 = {}", difference);
}

說明:

  • mod math_utils; 會自動從 src/math_utils.rs 文件中載入模組。
  • 我們可以直接在 main.rs 文件中使用 math_utils 模組中的函數。

三、使用嵌套模組

Rust 支援嵌套模組,使得開發者可以將程式碼組織得更為層次化。嵌套模組有助於更好地將功能模組化,特別是在處理大型專案時。

範例:嵌套模組

假設我們要實現一個數學工具,其中包括基本運算與進階運算,我們可以通過嵌套模組來組織這些功能。

src/math_utils.rs 文件:

pub mod basic_ops {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    pub fn subtract(a: i32, b: i32) -> i32 {
        a - b
    }
}

pub mod advanced_ops {
    pub fn power(base: i32, exp: u32) -> i32 {
        base.pow(exp)
    }

    pub fn factorial(n: u32) -> u32 {
        (1..=n).product()
    }
}

src/main.rs 文件:

mod math_utils;

fn main() {
    let sum = math_utils::basic_ops::add(3, 7);
    let power = math_utils::advanced_ops::power(2, 3);
    println!("3 + 7 = {}", sum);
    println!("2 ^ 3 = {}", power);
}

說明:

  • basic_opsadvanced_opsmath_utils 模組內的嵌套模組,分別用來處理基本運算與進階運算。
  • 我們在 main 中通過 math_utils::basic_ops::add 以及 math_utils::advanced_ops::power 來調用不同模組中的函數。

四、模組的訪問控制

Rust 中的模組預設是私有的,這意味著模組中的函數和結構無法被外部直接訪問。可以使用 pub 關鍵字來控制訪問權限。

  • 私有(預設):只有在同一模組內才能訪問。
  • 公開(pub:可以在模組外部訪問。

範例:模組的私有與公開

mod math_utils {
    fn private_fn() {
        println!("這是私有函數");
    }

    pub fn public_fn() {
        println!("這是公開函數");
        private_fn(); // 私有函數可以在模組內部調用
    }
}

fn main() {
    math_utils::public_fn();
    // math_utils::private_fn(); // 錯誤:無法在模組外部訪問私有函數
}

說明:

  • private_fn 是私有函數,只能在 math_utils 模組內部使用。
  • public_fn 是公開函數,外部可以通過 math_utils::public_fn 調用它。

五、套件管理與 Cargo

Cargo 是 Rust 的建構系統和套件管理器,能幫助你自動處理依賴項目、編譯專案、進行測試等。Rust 的專案結構通常依賴於 Cargo 來管理。

使用 Cargo 建立專案

你可以使用 Cargo 命令來建立一個新的專案:

cargo new my_project

這會自動建立一個帶有 Cargo.toml 文件的專案目錄。Cargo.toml 文件是專案的設定檔,包含專案的依賴項目、版本等資訊。

Cargo.toml 文件範例:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0"  # 這裡可以指定專案的依賴項目

添加依賴

在 Rust 專案中,通過修改 Cargo.toml 文件來添加外部依賴。例如,假設我們需要使用 serde 庫來處理 JSON,可以在 Cargo.toml 中加入:

[dependencies]
serde = "1.0"
serde_json = "1.0"

然後,運行 cargo build 來自動下載並編譯依賴。


六、完整範例:使用模組與 Cargo

最後,我們來看一個完整的範例,這個範例展示了如何使用模組系統以及 Cargo 管理專案。

src/main.rs

mod math_utils;

fn main() {
    let sum = math_utils::basic_ops::add(5, 15);
    let factorial = math_utils::advanced_ops::factorial(5);

    println!("5 + 15 = {}", sum);
    println!("5! = {}", factorial);
}

src/math_utils.rs

pub mod basic_ops {
    pub fn add(a: i32, b: i32) -> i32 {
        a

 + b
    }
}

pub mod advanced_ops {
    pub fn factorial(n: u32) -> u32 {
        (1..=n).product()
    }
}

Cargo.toml

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]

運行範例

運行以下命令來編譯和運行該專案:

cargo run

輸出結果將會是:

5 + 15 = 20
5! = 120

七、模組引用常見的錯誤整理

在 Rust 開發中,特別是當專案逐漸增大並分拆為多個模組和文件時,開發者常會遇到一些模組相關的錯誤。以下是一些常見的錯誤訊息及其解決方法,幫助你在開發過程中更快地排除錯誤。

1. 模組找不到(not found)

錯誤訊息示例:

error[E0583]: file not found for module `math_utils`
 --> src/main.rs:1:5
  |
1 | mod math_utils;
  |     ^^^^^^^^^^
  |
  = help: to create the module `math_utils`, create file "src/math_utils.rs" or "src/math_utils/mod.rs"

原因:

  • 這個錯誤通常發生在 Rust 找不到指定的模組文件時,可能是因為文件名稱或路徑不正確。

解決方法:

  • 檢查模組的定義和文件路徑是否一致。如果使用 mod math_utils;,確保存在 src/math_utils.rs 文件或 src/math_utils/mod.rs 文件。
  • 如果模組放在子目錄中,如 src/utils/math_utils.rs,則應在主文件中正確引用:mod utils::math_utils;

2. 模組未公開(module is private)

錯誤訊息示例:

error[E0603]: module `math_utils` is private
 --> src/main.rs:4:5
  |
4 | let sum = math_utils::add(5, 3);
  |          ^^^^^^^^^^^^^^^^^^
  |
  = note: the module `math_utils` is defined here

原因:

  • 模組或模組中的項目預設是私有的,無法在外部使用。

解決方法:

  • 在模組定義前加上 pub 關鍵字,使模組公開。例如,將 mod math_utils 改為 pub mod math_utils

  • 對於需要公開的函數或結構體,確保使用 pub 關鍵字標記。例如:

    pub mod math_utils {
        pub fn add(a: i32, b: i32) -> i32 {
            a + b
        }
    }
    

3. 無法在模組外部訪問私有函數

錯誤訊息示例:

error[E0603]: function `private_fn` is private
 --> src/main.rs:5:5
  |
5 | math_utils::private_fn();
  |            ^^^^^^^^^^^^

原因:

  • 嘗試從模組外部訪問私有函數。

解決方法:

  • 檢查函數定義,並將需要公開的函數加上 pub 關鍵字。例如:

    mod math_utils {
        fn private_fn() {
            println!("這是私有函數");
        }
    
        pub fn public_fn() {
            println!("這是公開函數");
            private_fn(); // 私有函數在模組內部可以正常調用
        }
    }
    
    fn main() {
        math_utils::public_fn();
        // math_utils::private_fn(); // 錯誤:無法在模組外部訪問私有函數
    }
    

4. use 語句找不到項目(unresolved import)

錯誤訊息示例:

error[E0432]: unresolved import `math_utils::add`
 --> src/main.rs:2:5
  |
2 | use math_utils::add;
  |     ^^^^^^^^^^^^^^^ no `add` in the root

原因:

  • use 語句引用的模組或項目不存在或未公開。

解決方法:

  • 確保被引用的模組或函數已經正確定義並公開,並且路徑無誤。例如,如果 add 函數位於 basic_ops 子模組內,應使用正確路徑:

    use math_utils::basic_ops::add;
    
    fn main() {
        let sum = add(5, 3);
        println!("5 + 3 = {}", sum);
    }
    

5. 重複定義模組(duplicate definitions)

錯誤訊息示例:

error[E0255]: the name `math_utils` is defined multiple times
 --> src/main.rs:1:5
  |
1 | mod math_utils;
  |     ^^^^^^^^^^ `math_utils` defined here
2 | mod math_utils;
  |     ^^^^^^^^^^ `math_utils` redefined here
  |
  = note: `math_utils` must be defined only once in the module scope

原因:

  • 模組被重複定義,可能是文件多次引用了相同模組。

解決方法:

  • 檢查模組定義是否重複,移除多餘的定義,確保每個模組僅被引用一次。

結論

Rust 的模組系統和套件管理工具 Cargo 是組織代碼和管理依賴的強大工具。通過模組化,你可以更好地將程式碼邏輯分離,增加可讀性與可維護性。Cargo 則為你提供了自動化的專案管理和依賴處理,使 Rust 的開發流程更加高效。


上一篇
[Day 10] 錯誤處理:探討Rust 的 Result 與 Option
下一篇
[Day 12] Cargo:Rust 的建置工具與套件管理器
系列文
從 Python 開發者的角度學習 Rust —— 從語法基礎到實戰應用30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言